// 变量声明
/*let和const是JavaScript里相对较新的变量声明方式。 像我们之前提到过的， let在很多方面与var是相似的，
但是可以帮助大家避免在JavaScript里常见一些问题。 const是对let的一个增强，它能阻止对一个变量再次赋值。
因为TypeScript是JavaScript的超集，所以它本身就支持let和const。 下面我们会详细说明这些新的声明方式以及为什么推荐使用它们来代替 var。
如果你之前使用JavaScript时没有特别在意，那么这节内容会唤起你的回忆。 如果你已经对 var声明的怪异之处了如指掌，那么你可以轻松地略过这节。*/


// var 声明
// 一直以来我们都是通过var关键字定义JavaScript变量。
var a = 10;

// 我们也可以在函数内部定义变量：
/*function f() {
    var message = "Hello, world!";

    return message;
}*/

// 并且我们也可以在其它函数内部访问相同的变量。
/*function f() {
    var a = 10;
    return function g() {
        var b = a + 1;
        return b;
    }
}

var g = f();
g();*/ // returns 11;

// 上面的例子里，g可以获取到f函数里定义的a变量。 每当 g被调用时，它都可以访问到f里的a变量。 即使当 g在f已经执行完后才被调用，
// 它仍然可以访问及修改a。
/*function f() {
    var a = 1;

    a = 2;
    var b = g();
    a = 3;

    return b;

    function g() {
        return a;
    }
}

f();*/ // returns 2


// 作用域规则。对于熟悉其它语言的人来说，var声明有些奇怪的作用域规则。 看下面的例子：
/*function f(shouldInitialize: boolean) {
    if (shouldInitialize) {
        var x = 10;
    }

    return x;
}*/

/*f(true);  // returns '10'
f(false); // returns 'undefined'*/
/*有些读者可能要多看几遍这个例子。 变量 x是定义在*if语句里面*，但是我们却可以在语句的外面访问它。
这是因为 var声明可以在包含它的函数，模块，命名空间或全局作用域内部任何位置被访问（我们后面会详细介绍），
包含它的代码块对此没有什么影响。 有些人称此为* var作用域或函数作用域*。 函数参数也使用函数作用域。

这些作用域规则可能会引发一些错误。 其中之一就是，多次声明同一个变量并不会报错：*/
/*
function sumMatrix(matrix: number[][]) {
    var sum = 0;
    for (var i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (var i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}*/
// 这里很容易看出一些问题，里层的for循环会覆盖变量i，因为所有i都引用相同的函数作用域内的变量。 有经验的开发者们很清楚，这些问题可能在代码审查时漏掉，引发无穷的麻烦。


// 捕获变量怪异之处
for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

// 一个通常的解决方法是使用立即执行的函数表达式（IIFE）来捕获每次迭代时i的值：
/*for (var i = 0; i < 10; i++) {
    // capture the current state of 'i'
    // by invoking a function with its current value
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
}*/

// 这种奇怪的形式我们已经司空见惯了。 参数 i会覆盖for循环里的i，但是因为我们起了同样的名字，所以我们不用怎么改for循环体里的代码。


// let 声明
// 块作用域
/*当用let声明一个变量，它使用的是词法作用域或块作用域。 不同于使用 var声明的变量那样可以在包含它们的函数外访问，块作用域变量在包含它们的块或for循环之外是不能访问的。*/
/*
function f(input: boolean) {
    let a = 100;

    if (input) {
        // Still okay to reference 'a'
        let b = a + 1;
        return b;
    }

    // Error: 'b' doesn't exist here
    return b;
}*/

// 在catch语句里声明的变量也具有同样的作用域规则。
/*try {
    throw "oh no!";
}
catch (e) {
    console.log("Oh well.");
}

// Error: 'e' doesn't exist here
console.log(e);*/

/*拥有块级作用域的变量的另一个特点是，它们不能在被声明之前读或写。 虽然这些变量始终“存在”于它们的作用域里，
但在直到声明它的代码之前的区域都属于 暂时性死区。 它只是用来说明我们不能在 let语句之前访问它们，幸运的是TypeScript可以告诉我们这些信息。*/
// a++; // illegal to use 'a' before it's declared;
// let a;

// 重定义及屏蔽
// 我们提过使用var声明时，它不在乎你声明多少次；你只会得到1个
// 在上面的例子里，所有x的声明实际上都引用一个相同的x，并且这是完全有效的代码。 这经常会成为bug的来源。 好的是， let声明就不会这么宽松了。
// 并不是说块级作用域变量不能用函数作用域变量来声明。 而是块级作用域变量需要在明显不同的块里声明。
/*
function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }

    return x;
}

f(false, 0); // returns 0
f(true, 0);  // returns 100*/

/*在一个嵌套作用域里引入一个新名字的行为称做屏蔽。 它是一把双刃剑，它可能会不小心地引入新问题，同时也可能会解决一些错误。
例如，假设我们现在用 let重写之前的sumMatrix函数。*/
/*function sumMatrix(matrix: number[][]) {
    let sum = 0;
    for (let i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (let i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}*/

// 通常来讲应该避免使用屏蔽，因为我们需要写出清晰的代码。 同时也有些场景适合利用它，你需要好好打算一下。

// 块级作用域变量的获取
/*在我们最初谈及获取用var声明的变量时，我们简略地探究了一下在获取到了变量之后它的行为是怎样的。 直观地讲，
每次进入一个作用域时，它创建了一个变量的 环境。 就算作用域内代码已经执行完毕，这个环境与其捕获的变量依然存在。*/
/*
function theCityThatAlwaysSleeps() {
    let getCity;

    if (true) {
        let city = "Seattle";
        getCity = function() {
            return city;
        }
    }

    return getCity();
}*/

/*因为我们已经在city的环境里获取到了city，所以就算if语句执行结束后我们仍然可以访问它。
回想一下前面setTimeout的例子，我们最后需要使用立即执行的函数表达式来获取每次for循环迭代里的状态。
实际上，我们做的是为获取到的变量创建了一个新的变量环境。 这样做挺痛苦的，但是幸运的是，你不必在TypeScript里这样做了。

当let声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境，而是针对 每次迭代都会创建这样一个新作用域。
这就是我们在使用立即执行的函数表达式时做的事，所以在 setTimeout例子里我们仅使用let声明就可以了。*/
/*
for (let i = 0; i < 10 ; i++) {
    setTimeout(function() {console.log(i); }, 100 * i);
}*/

// const 声明
// const 声明是声明变量的另一种方式。
const numLivesForCat = 9;
// 它们与let声明相似，但是就像它的名字所表达的，它们被赋值后不能再改变。 换句话说，它们拥有与 let相同的作用域规则，但是不能对它们重新赋值。

// let vs. const
/*现在我们有两种作用域相似的声明方式，我们自然会问到底应该使用哪个。 与大多数泛泛的问题一样，答案是：依情况而定。
使用最小特权原则，所有变量除了你计划去修改的都应该使用const。 基本原则就是如果一个变量不需要对它写入，
那么其它使用这些代码的人也不能够写入它们，并且要思考为什么会需要对这些变量重新赋值。 使用 const也可以让我们更容易的推测数据的流动。
跟据你的自己判断，如果合适的话，与团队成员商议一下。*/

// 解构

// 解构数组
// 最简单的解构莫过于数组的解构赋值了：
/*
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2*/

// 解构作用于已声明的变量会更好：
// swap variables
// [first, second] = [second, first];

// 作用于函数参数：
/*function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f(input);*/

// 你可以在数组里使用...语法创建剩余变量：
/*
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]*/

// 当然，由于是JavaScript, 你可以忽略你不关心的尾随元素：
/*
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1*/
// 或其它元素：
let [, second, , fourth] = [1, 2, 3, 4];


// 对象解构
/*let o = {
    a: "foo",
    b: 12,
    c: "bar"
};
let { a, b } = o;*/

// 就像数组解构，你可以用没有声明的赋值：
// ({ a, b } = { a: "baz", b: 101 });
// 注意，我们需要用括号将它括起来，因为Javascript通常会将以 { 起始的语句解析为一个块。
// 你可以在对象里使用...语法创建剩余变量：
/*
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;*/

// 属性重命名
// let { a: newName1, b: newName2 } = o;

// 令人困惑的是，这里的冒号不是指示类型的。 如果你想指定它的类型， 仍然需要在其后写上完整的模式。
// let {a, b}: {a: string, b: number} = o;

// 默认值
// 默认值可以让你在属性为 undefined 时使用缺省值：
/*
function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}*/

// 函数声明
// 解构也能用于函数声明。 看以下简单的情况：
/*type C = { a: string, b?: number }
function f({ a, b }: C): void {
    // ...
}*/

// 但是，通常情况下更多的是指定默认值，解构默认值有些棘手。 首先，你需要在默认值之前设置其格式。
/*
function f({ a="", b=0 } = {}): void {
    // ...
}
f();*/

// 其次，你需要知道在解构属性上给予一个默认或可选的属性用来替换主初始化列表。 要知道 C 的定义有一个 b 可选属性：
/*
function f({ a, b = 0 } = { a: "" }): void {
    // ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to {a: ""}, which then defaults b = 0
f({}); // error, 'a' is required if you supply an argument*/

/*要小心使用解构。 从前面的例子可以看出，就算是最简单的解构表达式也是难以理解的。 尤其当存在深层嵌套解构的时候，
就算这时没有堆叠在一起的重命名，默认值和类型注解，也是令人难以理解的。 解构表达式要尽量保持小而简单。 你自己也可以直接使用解构将会生成的赋值表达式。*/


// 展开
// 展开操作符正与解构相反。 它允许你将一个数组展开为另一个数组，或将一个对象展开为另一个对象。 例如：
/*
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];*/

// 你还可以展开对象：
/*
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };*/

/*search的值为{ food: "rich", price: "$$", ambiance: "noisy" }。 对象的展开比数组的展开要复杂的多。 像数组展开一样，
它是从左至右进行处理，但结果仍为对象。 这就意味着出现在展开对象后面的属性会覆盖前面的属性。*/

/*对象展开还有其它一些意想不到的限制。 首先，它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时，你会丢失其方法：*/
/*
class C {
    p = 12;
    m() {
    }
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!*/
